interface Window {
  MyPromise: typeof MyPromise
}

enum MyPromiseState {
  PENDING = 'pending',
  RESOLVED = 'resolved',
  REJECTED = 'rejected',
}

type ResolveFn = (data?: any) => void
type RejectFn = (reason?: any) => void
type MyPromiseCallback = (resolve: ResolveFn, reject: RejectFn) => void
type MyPromiseThenReturn = MyPromise | any

class MyPromise {
  private ['[[PromiseState]]']: MyPromiseState = MyPromiseState.PENDING
  private ['[[PromiseResult]]']: any = undefined
  #resolveQueue: Function[] = []
  #rejectQueue: Function[] = []

  constructor(callback: MyPromiseCallback) {
    const resolve: ResolveFn = data => {
      if (this['[[PromiseState]]'] === MyPromiseState.PENDING) {
        this['[[PromiseState]]'] = MyPromiseState.RESOLVED
        this['[[PromiseResult]]'] = data
        const run = () => {
          let fn = this.#resolveQueue.shift()
          while (fn) {
            fn(data)
            fn = this.#resolveQueue.shift()
          }
        }
        const ob = new MutationObserver(run)
        ob.observe(document.body, { attributes: true })
        document.body.setAttribute('myP', 'ob')
      }
    }
    const reject: RejectFn = reason => {
      if (this['[[PromiseState]]'] === MyPromiseState.PENDING) {
        this['[[PromiseState]]'] = MyPromiseState.REJECTED
        this['[[PromiseResult]]'] = reason
        const run = () => {
          let fn = this.#rejectQueue.shift()
          while (fn) {
            fn(reason)
            fn = this.#rejectQueue.shift()
          }
        }
        const ob = new MutationObserver(run)
        ob.observe(document.body, { attributes: true })
        document.body.setAttribute('myP', 'ob')
      }
    }
    callback(resolve, reject)
  }

  then(onResolve?: (res: any) => MyPromiseThenReturn, onReject?: (err: any) => MyPromiseThenReturn): MyPromise {
    return new MyPromise((resolve, reject) => {
      const resolveFn: ResolveFn = data => {
        const result = onResolve(data)
        if (result instanceof MyPromise) {
          result.then(
            res => resolve(res),
            err => reject(err)
          )
        } else {
          resolve(result)
        }
      }
      const rejectFn: RejectFn = reason => {
        const result = onReject(reason)
        if (result instanceof MyPromise) {
          result.then(
            res => resolve(res),
            err => reject(err)
          )
        } else {
          resolve(result)
        }
      }
      switch (this['[[PromiseState]]']) {
        case MyPromiseState.PENDING:
          this.#resolveQueue.push(resolveFn)
          this.#rejectQueue.push(rejectFn)
          break
        case MyPromiseState.RESOLVED:
          onResolve(this['[[PromiseResult]]'])
          break
        case MyPromiseState.REJECTED:
          onReject(this['[[PromiseResult]]'])
          break
      }
    })
  }

  static all = (pArr: MyPromise[]) => {
    return new MyPromise((resolve, reject) => {
      let len = 0
      const result: any[] = []
      pArr.forEach((p, i) => {
        p.then(
          res => {
            len++
            result[i] = res
            if (len === pArr.length) {
              resolve(result)
            }
          },
          err => {
            reject(err)
          }
        )
      })
    })
  }

  static race = (pArr: MyPromise[]) => {
    return new MyPromise((resolve, reject) => {
      pArr.forEach(p => p.then(resolve, reject))
    })
  }
}

window.MyPromise = MyPromise
